import { Pool as GenericPool, Options, createPool } from 'generic-pool'
import { Redis, RedisCommander, RedisOptions } from 'ioredis'
import { merge } from 'lodash'
import { PoolConnection, poolConnectionMap } from './PoolConnection'

/**
 * Redis连接池配置项
 */
export interface PoolOptions extends RedisOptions {
    /**
     * 连接池配置项
     */
    pool?: Options
}

/**
 * Redis连接池
 */
export class Pool {
    protected clientOptions: RedisOptions

    protected pool: GenericPool<PoolConnection>

    constructor(options: PoolOptions = {}) {
        const { pool, ...opts } = merge({}, options)
        this.clientOptions = opts
        this.pool = createPool(
            {
                create: async () => {
                    const client = new PoolConnection({
                        ...this.clientOptions,
                        lazyConnect: true,
                    })
                    await client.connect()
                    poolConnectionMap.set(client, this.pool)
                    return client
                },
                destroy: async (client) => {
                    Redis.prototype.disconnect.call(client)
                },
                validate: (client) => client.ping().then((res) => res === 'PONG'),
            },
            pool
        )
    }

    getConnection() {
        return this.pool.acquire()
    }

    /**
     * 关闭连接池和池中的所有连接
     * @returns
     */
    close() {
        return this.pool.clear()
    }
}

export interface Pool extends RedisCommander {}

//映射Redis访问方法到连接池
const prototype = Object.getPrototypeOf(Redis.prototype)
Object.entries(prototype).forEach(([name, member]) => {
    if (typeof member !== 'function') return
    Object.defineProperty(Pool.prototype, name, {
        value: async function (this: Pool, ...args: any[]) {
            const conn = await this.getConnection()
            try {
                return await member.call(conn, ...args)
            } finally {
                await conn.release()
            }
        },
    })
})
